﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using dasBlog.Storage;
using System.Collections.ObjectModel;
using newtelligence.DasBlog.Runtime;
using System.Xml;
using System.IO;
using System.Threading;
using System.Collections;
using System.Diagnostics;

namespace dasBlog.Services.Interop
{
    class StorageBusBlogDataService : IBlogDataService, IBlogDataServiceInitialize
    {
        private Moniker scopeUri;
        private Moniker postsUri;
        private ILoggingDataService loggingService;
        private TrackingTools trackingTools;

        public StorageBusBlogDataService()
        {
        }

        public StorageBusBlogDataService(string scopeName):this()
        {
            Initialize(scopeName, null);
        }

        public void Initialize(string scopeName, ILoggingDataService logService)
        {
            this.scopeUri = Moniker.FromScopeName(scopeName);
            this.postsUri = new Moniker(scopeName, PathSegmentName.Posts);
            this.loggingService = logService;
            trackingTools = new TrackingTools(this, loggingService);
        }


        public Entry GetEntry(string entryId)
        {
            Moniker entryUri = new Moniker(postsUri, entryId);
            TextEntry entry = entryUri.Get<TextEntry>();
            if (entry != null)
            {
                return EntryFromTextEntry(entry);
            }
            else
            {
                return null;
            }
        }

        public Entry GetEntryForEdit(string entryId)
        {
            return GetEntry(entryId);
        }

        public EntryCollection GetEntriesForDay(DateTime start, TimeZone tz, string acceptLanguages, int maxDays, int maxEntries, string categoryName)
        {
            return new EntryCollection((from entry in 
                                            postsUri.Select<TextEntry>(
                                                                MonikerMatch.Exact,
                                                                new DateRangeQueryDescription(DateTime.MinValue, start.Date.AddDays(1)))
                          orderby entry.Created descending 
                          where entry.Language == null || acceptLanguages.Contains(entry.Language)
                          select EntryFromTextEntry(entry)).Take(maxEntries).ToArray());
        }

        public EntryCollection GetEntriesForMonth(DateTime summary, TimeZone tz, string acceptLanguages)
        {
            return new EntryCollection((from te in postsUri.Select<TextEntry>(MonikerMatch.Exact,
                                                        new DateRangeQueryDescription(tz.ToLocalTime(new DateTime(summary.Year, summary.Month, 1)),
                                                                                      tz.ToLocalTime(new DateTime(summary.Year, summary.Month, 1).AddMonths(1).AddDays(-1))))
                                        where te.Language == null || te.Language == acceptLanguages || acceptLanguages.Contains(te.Language)
                                        select EntryFromTextEntry(te)).ToArray());
        }

        public EntryCollection GetAllEntries(bool infoOnly)
        {
            if (!infoOnly)
            {
                return new EntryCollection((from te in postsUri.SelectAll<TextEntry>()
                                            select EntryFromTextEntry(te)).ToArray());
            }
            else
            {
                return new EntryCollection((from te in postsUri.Select<TextEntry>(MonikerMatch.Exact,new IdOnlyQueryDescription())
                                            select EntryFromTextEntry(te)).ToArray());
            }
        }

        private LogEntry LogEntryFromTracking(Tracking trk )
        {
            return new LogEntry
            {
                RequestTime = DateTime.Now,
                ReferrerUrl = trk.Referer,
                ReferrerTitle = trk.RefererTitle,
                ReferrerName = trk.RefererBlogName,
                UrlRequested = trk.PermaLink,
                UserIP = trk.RefererIPAddress,
                TargetId = new Moniker(postsUri, trk.TargetEntryId).ToString(),
                TargetTitle = trk.TargetTitle,
                TrackingType = trk.TrackingType.ToString(),
            };
        }

        private Tracking TrackingFromLogEntry(LogEntry lge)
        {
            return new Tracking
            {
                TrackingType = (TrackingType)Enum.Parse(typeof(TrackingType),lge.TrackingType),
                Referer = lge.ReferrerUrl,
                RefererBlogName = lge.ReferrerName,
                RefererTitle = lge.ReferrerTitle,
                PermaLink = lge.UrlRequested,
                RefererIPAddress = lge.UserIP,
                TargetEntryId = new Moniker(lge.TargetId).ItemId,
                TargetTitle = lge.TargetTitle,
            };
        }

        private TextEntry TextEntryFromEntry(Entry entry)
        {
            TextEntry textEntry = new TextEntry
            {
                AllowComments = entry.AllowComments,
                CategoriesAsString = entry.Categories,
                Content = entry.Content,
                Created = entry.CreatedUtc,
                Featured = entry.ShowOnFrontPage,
                Language = entry.Language,
                LastChange = entry.ModifiedUtc,
                Author = new AuthorDescription { DisplayName = entry.Author },
                Published = entry.IsPublic,
                Summary = entry.Description,
                Syndicated = entry.Syndicated,
                Title = entry.Title,
                Reference = entry.Link,
                Id = new Moniker(this.postsUri, entry.EntryId)
            };
            return textEntry;
        }

        private TextEntry TextEntryFromComment(Comment entry)
        {
            return new TextEntry
            {
                Content = entry.Content,
                Created = entry.CreatedUtc,
                LastChange = entry.ModifiedUtc,
                Author = new AuthorDescription { DisplayName = entry.Author, Email = entry.AuthorEmail },
                Published = entry.IsPublic,
                Title = entry.TargetTitle,
                Parent = new Moniker(this.postsUri,entry.TargetEntryId).ToString(),
                Id = new Moniker(this.postsUri,entry.TargetEntryId, PathSegmentName.Comments, entry.EntryId)
            };
        }

        private static Entry EntryFromTextEntry(TextEntry entry)
        {
            return new Entry
            {
                AllowComments = entry.AllowComments.HasValue?entry.AllowComments.Value:false,
                Author = entry.Author.DisplayName,
                Categories = entry.CategoriesAsString,
                Content = entry.Content,
                CreatedUtc = entry.Created,
                Description = entry.Summary,
                EntryId = entry.Id.ItemId,
                Syndicated = entry.Published.HasValue ? entry.Published.Value : false,
                IsPublic = entry.Published.HasValue ? entry.Published.Value : false,
                Language = entry.Language,
                Title = entry.Title,
                ShowOnFrontPage = entry.Featured.HasValue?entry.Featured.Value:false
            };
        }

        private static Comment CommentFromTextEntry(TextEntry entry)
        {
            return new Comment
            {
                Author = entry.Author.DisplayName,
                AuthorEmail = entry.Author.Email,
                TargetEntryId = new Moniker(entry.Parent).ItemId,
                Content = entry.Content,
                CreatedUtc = entry.Created,
                EntryId = entry.Id.ItemId,
                IsPublic = entry.Published.GetValueOrDefault(),
                Language = entry.Language,
                ModifiedUtc = entry.LastChange,
            };
        }

        public EntryCollection GetEntriesForCategory(string categoryName, string acceptLanguages)
        {
            return new EntryCollection((from entry in postsUri.Select<TextEntry>(MonikerMatch.Exact,
                                                             new TagQueryDescription(categoryName))
                                        where entry.Language == null || acceptLanguages.Contains(entry.Language)
                                        orderby entry.Created
                                        select EntryFromTextEntry(entry)).ToArray());
            
        }

        public EntryCollection GetEntriesForUser(string user)
        {
            return null;
        }

        public DateTime[] GetDaysWithEntries(TimeZone tz)
        {
            var entries = postsUri.Aggregate(MonikerMatch.Exact, "datecreated");
            return (from agg in entries select DateTime.Parse(agg.Value)).ToArray();
        }

        public DayEntry GetDayEntry(DateTime date)
        {
            return new DayEntry
            {
                DateUtc = date,
                Entries = GetEntriesForDay(date, TimeZone.CurrentTimeZone, null, int.MaxValue, int.MaxValue, null),
                DateLocalTime = date.ToLocalTime(),
            };
        }

        public DayExtra GetDayExtra(DateTime date)
        {
            IList<TextEntry> entriesOnDay = 
                postsUri.Select<TextEntry>(
                                    MonikerMatch.Exact,
                                    new DateRangeQueryDescription(date.Date, date.Date.AddDays(1).AddMilliseconds(-1)));

            var allComments = new CommentCollection(
                              (from TextEntry post in entriesOnDay
                               from TextEntry comment in new Moniker(post.Id, PathSegmentName.Comments).SelectAll<TextEntry>()
                               select CommentFromTextEntry(comment)).ToArray());

            DayExtra extra = new DayExtra();
            extra.DateUtc = date.ToUniversalTime();
            extra.Comments.AddRange(allComments);
            return extra;
        }

        public void DeleteEntry(string entryId, CrosspostSiteCollection crosspostSites)
        {
            new Moniker(postsUri, entryId).Delete();
        }

        public EntrySaveState SaveEntry(Entry entry, params object[] trackingInfos)
        {
            if (!entry.IsNew)
            {
                Moniker entryMk = new Moniker(postsUri, entry.EntryId);
                TextEntry textEntry = entryMk.Get<TextEntry>();
                if (textEntry != null)
                {
                    Entry currentEntry = EntryFromTextEntry(textEntry);
                    if (currentEntry != null && !currentEntry.Equals(entry))
                    {
                        // we will only change the mod date if there has been a change to a few things
                        if (currentEntry.CompareTo(entry) == 1)
                        {
                            currentEntry.ModifiedUtc = DateTime.Now.ToUniversalTime();
                        }
                        currentEntry.Categories = entry.Categories;
                        currentEntry.Syndicated = entry.Syndicated;
                        currentEntry.Content = entry.Content;
                        currentEntry.CreatedUtc = entry.CreatedUtc;
                        currentEntry.Description = entry.Description;
                        currentEntry.anyAttributes = entry.anyAttributes;
                        currentEntry.anyElements = entry.anyElements;
                        currentEntry.Author = entry.Author;
                        currentEntry.IsPublic = entry.IsPublic;
                        currentEntry.Language = entry.Language;
                        currentEntry.AllowComments = entry.AllowComments;
                        currentEntry.Link = entry.Link;
                        currentEntry.ShowOnFrontPage = entry.ShowOnFrontPage;
                        currentEntry.Title = entry.Title;

                        currentEntry.Crossposts.Clear();
                        currentEntry.Crossposts.AddRange(entry.Crossposts);
                        currentEntry.Attachments.Clear();
                        currentEntry.Attachments.AddRange(entry.Attachments);

                        entryMk.Store<TextEntry>(TextEntryFromEntry(currentEntry));
                        trackingTools.RunActions(trackingInfos, currentEntry);
                        return EntrySaveState.Updated;
                    }
                    else
                    {
                        throw new InvalidOperationException("Item doesn't exist");
                    }
                }
            }
            else
            {
                postsUri.Store<TextEntry>(TextEntryFromEntry(entry));
                trackingTools.RunActions(trackingInfos, entry);
                return EntrySaveState.Added;
            }

            return EntrySaveState.Failed;
        }

        public CategoryCacheEntryCollection GetCategories()
        {
            return new CategoryCacheEntryCollection(
                (from PropertyAggregate agg in postsUri.Aggregate(MonikerMatch.Exact, "tag")
                 select new CategoryCacheEntry
                 {
                     Name = agg.Value,
                     IsPublic = true
                 }).ToArray());
        }

        private void InternalSendMail(SendMailInfo info)
        {
            try
            {
                info.SendMyMessage();
            }
            catch (Exception e)
            {
                ErrorTrace.Trace(TraceLevel.Error, e);
                if (loggingService != null)
                {
                    //CDO is very touchy and it's useful to get ALL the inner exceptions to diagnose the problem.
                    string exMessage = e.ToString();

                    Exception inner = e.InnerException;
                    while (inner != null)
                    {
                        exMessage += " INNER: " + inner.ToString();
                        inner = inner.InnerException;
                    }

                    loggingService.AddEvent(new EventDataItem(EventCodes.SmtpError, exMessage.Replace("\n", "<br>"), "InternalSendMail"));
                }
            }
        }

        public void RunActions(object[] actions)
        {
            trackingTools.RunActions(actions,null);
        }

        public void AddTracking(Tracking tracking, params object[] actions)
        {
            ThreadPool.QueueUserWorkItem(delegate(object o)
            {
                Moniker mkTrackings = new Moniker(postsUri, tracking.TargetEntryId, PathSegmentName.Trackings);
                mkTrackings.Store(LogEntryFromTracking(tracking));
                RunActions(actions);
            });
        }

        public void DeleteTracking(string entryId, string trackingPermalink, TrackingType trackingType)
        {
            ThreadPool.QueueUserWorkItem(delegate(object o)
            {
                Moniker mkTrackings = new Moniker(postsUri, entryId, PathSegmentName.Trackings);
                foreach (var tracking in mkTrackings.Select<LogEntry>(MonikerMatch.Exact, new QueryDescription("ReferrerUrl", new QueryArgument("ReferrerUrl", trackingPermalink))))
                {
                    tracking.Id.Delete();
                }
            });
        }

        public TrackingCollection GetTrackingsFor(string entryId)
        {
            TrackingCollection trackings = new TrackingCollection();
            Moniker mkTrackings = new Moniker(postsUri, entryId, PathSegmentName.Trackings);
            foreach (LogEntry lge in mkTrackings.SelectAll<LogEntry>())
            {
                trackings.Add(TrackingFromLogEntry(lge));
            }
            return trackings;
        }

        public void AddComment(Comment comment, params object[] actions)
        {
            new Moniker(postsUri, comment.TargetEntryId, PathSegmentName.Comments).Store(TextEntryFromComment(comment));
            RunActions(actions);
        }

        public Comment GetCommentById(string entryId, string commentIdx)
        {
            TextEntry entry = new Moniker(postsUri, entryId, PathSegmentName.Comments, commentIdx).Get<TextEntry>();
            if (entry != null)
            {
                return CommentFromTextEntry(entry);
            }
            else
            {
                return null;
            }
        }

        public void ApproveComment(string entryId, string commentIdx)
        {
            Moniker commentId = new Moniker(postsUri, entryId, PathSegmentName.Comments, commentIdx);
            TextEntry entry = commentId.Get<TextEntry>();
            if (entry != null)
            {
                entry.Published = true;
                commentId.Store<TextEntry>(entry);
            }
        }

        public void DeleteComment(string entryId, string commentIdx)
        {
            new Moniker(postsUri, entryId, PathSegmentName.Comments, commentIdx).Delete();
        }

        public CommentCollection GetCommentsFor(string entryId)
        {
            return new CommentCollection(
                              (from TextEntry comment in new Moniker(postsUri, entryId, PathSegmentName.Comments).Select<TextEntry>(
                                                                                   MonikerMatch.Exact,
                                                                                   null)
                               select CommentFromTextEntry(comment)).ToArray());
        }

        public CommentCollection GetPublicCommentsFor(string entryId)
        {
            return new CommentCollection(
                              (from TextEntry comment in new Moniker(postsUri, entryId, PathSegmentName.Comments).Select<TextEntry>(
                                                                                   MonikerMatch.Exact,
                                                                                   null)
                               where comment.Published == true
                               select CommentFromTextEntry(comment)).ToArray());
        }

        public CommentCollection GetCommentsFor(string entryId, bool allComments)
        {
            return GetCommentsFor(entryId);
        }

        public CommentCollection GetAllComments()
        {
            return new CommentCollection(
                              (from TextEntry post in postsUri.SelectAll<TextEntry>()
                               from TextEntry comment in new Moniker(post.Id, PathSegmentName.Comments).SelectAll<TextEntry>()
                               select CommentFromTextEntry(comment)).ToArray());
        }

        public DateTime GetLastEntryUpdate()
        {
            return DateTime.Now;
        }

        public DateTime GetLastCommentUpdate()
        {
            return DateTime.Now;
        }

        public EntryCollection /*IBlogDataService*/ GetEntries(
			DayEntryCollection.CriteriaHandler dayEntryCriteria, 
            Predicate<Entry> entryCriteria,
            int maxDays, int maxEntries)
        {
            IStorageNode storageNode = StorageBus.Current.FindNode(postsUri);
            return new EntryCollection((from entry in 
                                            postsUri.SelectAll<TextEntry>()
                                        orderby entry.Created descending
                                        select EntryFromTextEntry(entry)).Where((e)=>entryCriteria(e)).Take(maxEntries).ToArray());
        }

        public string AddFile(string entryId, string fileName, string contentType, long contentLength, System.IO.Stream contentData)
        {
            Moniker mediaUri;

            if (string.IsNullOrEmpty(entryId))
            {
                mediaUri = new Moniker(scopeUri, PathSegmentName.Media);
            }
            else
            {
                mediaUri = new Moniker(postsUri, entryId, PathSegmentName.Media);
            }
            IStreamStorageNode node = StorageBus.Current.FindNode(mediaUri) as IStreamStorageNode;
            if (node != null)
            {
                var result = node.SetStream(
                    new StreamStorageSetRequest
                    {
                        moniker = mediaUri,
                        Stream = contentData,
                        name = Path.GetFileName(fileName),
                        contentType = contentType
                    }
                );
            }
            return fileName;
        }

        public string[] GetFileList(string entryId)
        {
            Moniker mediaUri;
            if (string.IsNullOrEmpty(entryId))
            {
                mediaUri = new Moniker(scopeUri, PathSegmentName.Media);
            }
            else
            {
                mediaUri = new Moniker(postsUri, entryId, PathSegmentName.Media);
            }

            return (from item in mediaUri.SelectAll<StreamStorageInfo>()
                    select item.Id.ItemId).ToArray();
        }

        public void DeleteFiles(string entryId)
        {
            if (string.IsNullOrEmpty(entryId))
            {
                return;
            }
            Moniker mediaUri = new Moniker(postsUri, entryId, PathSegmentName.Media);
            var list = mediaUri.SelectAll<StreamStorageInfo>();
            foreach ( var item in list )
            {
                item.Id.Delete();
            }
            
        }
    }
}
